﻿using System.Collections.Generic;
using System.Linq;
using System.Text;
using eAtlantida.Tools.Generator.Sql;

namespace eAtlantida.Tools.Generator
{
    public class ServerGenerator
    {
        private readonly bool _isCreate;

        private readonly bool _isValidate;

        private readonly bool _isDelete;

        private readonly bool _isListing;

        private readonly bool _isSave;

        private readonly string name;

        private string singleName
        {
            get
            {
                if (name.EndsWith("es"))
                {
                    return name.Substring(0, name.Length - 2);
                }
                return name.Substring(0, name.Length - 1);
            }
        }

        private readonly IEnumerable<ColumnInformation> _columns;

        public ServerGenerator(IEnumerable<ColumnInformation> columns, string name, bool isCreate= true, bool isSave = true, bool isDelete = true, bool isListing = true, bool isValidate = true)
        {
            _isCreate = isCreate;
            _isValidate = isValidate;
            _isDelete = isDelete;
            _isListing = isListing;
            _isSave = isSave;
            _columns = columns;
            this.name = name;
        }

        public string GenerateEditModel()
        {
            var result = new StringBuilder();
            var singleNameWithFirstLowerChar = singleName.ToLowerFirstChar();

            result.AppendLine("using System.ComponentModel.DataAnnotations;");
            result.AppendLine("using BLL.Services.", name, ";");
            result.AppendLine("using Web.Infrastructure.Models;");
            result.AppendLine();
            result.AppendLine("namespace Web.Areas.Admin.Models.", name);
            result.AppendLine("{");
            result.AppendLine(1, "public class ", singleName, "EditModel : BaseModel");
            result.AppendLine(1, "{");
            result.AppendLine(2, "public ", singleName, "EditModel(", singleName, " ", singleNameWithFirstLowerChar, ")");
            result.AppendLine(2, "{");
            result.AppendLine(3, "ToModel(", singleNameWithFirstLowerChar, ", this);");
            result.AppendLine(2, "}");
            result.AppendLine();
            result.AppendLine(2, "public ", singleName, "EditModel()");
            result.AppendLine(2, "{");
            result.AppendLine(2, "}");
            result.AppendLine();
            result.AppendLine(2, "public static explicit operator ", singleName, "(", singleName, "EditModel model)");
            result.AppendLine(2, "{");
            result.AppendLine(3, "var ", singleNameWithFirstLowerChar, " = new ", singleName, "();");
            result.AppendLine(3, "ToEntity(model, ", singleNameWithFirstLowerChar, ");");
            result.AppendLine(3, "return ", singleNameWithFirstLowerChar, ";");
            result.AppendLine(2, "}");

            foreach(var column in _columns)
            {
                result.AppendLine();
                if (column.IsIdentity)
                {
                    result.AppendLine(2, "[UIHint(\"hidden\")]");
                }
                else
                {
                    if (GetCSharpType(column.DataTypeName, column.IsNullable) == "string")
                    {
                        result.AppendLine(2, "[StringLength(256)]");
                    }
                    result.AppendLine(2, "[Required]");
                    result.AppendLine(2, "[Display(Name = \"", column.Name, "\")]");
                }
                result.AppendLine(2, "public ", GetCSharpType(column.DataTypeName, column.IsNullable), " ", column.Name, " { get; set; }");
            }

            result.AppendLine(1, "}");
            result.Append("}");

            return result.ToString();
        }

        public string GenerateCreateModel()
        {
            var result = new StringBuilder();
            var singleNameWithFirstLowerChar = singleName.ToLowerFirstChar();

            result.AppendLine("using System.ComponentModel.DataAnnotations;");
            result.AppendLine("using BLL.Services.", name, ";");
            result.AppendLine("using Web.Infrastructure.Models;");
            result.AppendLine();
            result.AppendLine("namespace Web.Areas.Admin.Models.", name);
            result.AppendLine("{");
            result.AppendLine(1, "public class ", singleName, "CreateModel : BaseModel");
            result.AppendLine(1, "{");
            result.AppendLine(2, "public ", singleName, "CreateModel(", singleName, " ", singleNameWithFirstLowerChar, ")");
            result.AppendLine(2, "{");
            result.AppendLine(3, "ToModel(", singleNameWithFirstLowerChar, ", this);");
            result.AppendLine(2, "}");
            result.AppendLine();
            result.AppendLine(2, "public ", singleName, "CreateModel()");
            result.AppendLine(2, "{");
            result.AppendLine(2, "}");
            result.AppendLine();
            result.AppendLine(2, "public static explicit operator ", singleName, "(", singleName, "CreateModel model)");
            result.AppendLine(2, "{");
            result.AppendLine(3, "var ", singleNameWithFirstLowerChar, " = new ", singleName, "();");
            result.AppendLine(3, "ToEntity(model, ", singleNameWithFirstLowerChar, ");");
            result.AppendLine(3, "return ", singleNameWithFirstLowerChar, ";");
            result.AppendLine(2, "}");

            foreach (var column in _columns)
            {
                if (!column.IsIdentity)
                {
                    result.AppendLine();
                    if (GetCSharpType(column.DataTypeName, column.IsNullable) == "string")
                    {
                        result.AppendLine(2, "[StringLength(256)]");
                    }
                    result.AppendLine(2, "[Required]");
                    result.AppendLine(2, "[Display(Name = \"", column.Name, "\")]");
                    result.AppendLine(2, "public ", GetCSharpType(column.DataTypeName, column.IsNullable), " ", column.Name, " { get; set; }");
                }
            }

            result.AppendLine(1, "}");
            result.Append("}");

            return result.ToString();
        }

        public string GenerateController()
        {
            var result = new StringBuilder();
            var serviceName = "_" + name.ToLowerFirstChar() + "Service";

            result.AppendLine("using System.Collections.Generic;");
            result.AppendLine("using System.Diagnostics;");
            result.AppendLine("using System.Web.Mvc;");
            result.AppendLine("using BLL.Services;");
            result.AppendLine("using BLL.Services.", name, ";");
            if (_isListing)
            {
                result.AppendLine("using BLL.Services.", name, ".Listing;");
                result.AppendLine("using MvcContrib.Sorting;");
                result.AppendLine("using MvcContrib.UI.Grid;");
            }
            result.AppendLine("using Web.Areas.Admin.Infrastructure;");
            result.AppendLine("using Web.Areas.Admin.Models.", name, ";");
            if (_isListing)
            {
                result.AppendLine("using Web.Infrastructure;");
            }
            result.AppendLine("using Web.Infrastructure.Models;");
            result.AppendLine("using Web.Infrastructure.Security;");
            result.AppendLine();
            result.AppendLine("namespace Web.Areas.Admin.Controllers");
            result.AppendLine("{");
            result.AppendLine(1, "[AdminAuthorization]");
            result.AppendLine(1, "public partial class ", name, "Controller : AdminController");
            result.AppendLine(1, "{");
            result.AppendLine(2, "private readonly I", name, "Service ", serviceName, ";");
            result.AppendLine();
            result.AppendLine(2, "public ", name, "Controller(I", name, "Service ", name.ToLowerFirstChar(), "Service)");
            result.AppendLine(2, "{");
            result.AppendLine(3, serviceName, " = ", name.ToLowerFirstChar(), "Service;");
            result.AppendLine(2, "}");
            result.AppendLine();
            result.AppendLine(2, "public virtual ActionResult Index()");
            result.AppendLine(2, "{");
            result.AppendLine(3, "return BackBoneView(MVC.Admin.Shared.Views.Empty);");
            result.AppendLine(2, "}");

            if (_isListing)
            {
                result.AppendLine();
                result.AppendLine(2, "public virtual ActionResult ListingTemplate()");
                result.AppendLine(2, "{");
                result.AppendLine(3, "return BackBoneView(MVC.Admin.", name, ".Views.Listing);");
                result.AppendLine(2, "}");
                result.AppendLine();
                var firstNotIdentityField = _columns.FirstOrDefault(x => !x.IsIdentity);
                if (firstNotIdentityField != null)
                {
                    result.AppendLine(2, "[ListingAction(\"", firstNotIdentityField.Name, "\")]");
                }
                result.AppendLine(2, "public virtual ActionResult Listing(");
                result.AppendLine(3, "GridSortOptions sort, int size, int page, ", singleName, "Filter filter)");
                result.AppendLine(2, "{");
                result.AppendLine(3, "var model = ", serviceName, ".GetListing", name, "(");
                result.AppendLine(4, "page, size, sort.Column, sort.Direction != SortDirection.Descending ? 0 : 1, filter);");
                result.AppendLine(3, "return BackBoneJson(model);");
                result.AppendLine(2, "}");
            }

            if (_isCreate)
            {
                result.AppendLine();
                result.AppendLine(2, "[HttpGet]");
                result.AppendLine(2, "public virtual ActionResult Create()");
                result.AppendLine(2, "{");
                result.AppendLine(3, "return BackBoneView(MVC.Admin.Shared.Views.Empty);");
                result.AppendLine(2, "}");
                result.AppendLine();
                result.AppendLine(2, "[HttpGet]");
                result.AppendLine(2, "public virtual ActionResult CreateTemplate()");
                result.AppendLine(2, "{");
                result.AppendLine(3, "return BackBoneView(MVC.Admin.", name, ".Views.Create, new ", singleName, "CreateModel());");
                result.AppendLine(2, "}");
                result.AppendLine();
                result.AppendLine(2, "[HttpPost]");
                result.AppendLine(2, "public virtual ActionResult Create(", singleName, "CreateModel model)");
                result.AppendLine(2, "{");
                result.AppendLine(3, "if (!ModelState.IsValid)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "model.Errors = GetErrors;");
                result.AppendLine(4, "return Json(model);");
                result.AppendLine(3, "}");
                result.AppendLine(3, "var ", singleName.ToLowerFirstChar(), " = (", singleName, ")model;");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");
                result.AppendLine(4, serviceName, ".Create(", singleName.ToLowerFirstChar(),");");
                result.AppendLine(4, "model.SuccessMessage = \"", singleName, " has been successfully created.\";");
                result.AppendLine(4, "return Json(model);");
                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (ServiceException exception)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "ModelState.AddModelError(string.Empty, exception.Message);");
                result.AppendLine(4, "model.Errors = GetErrors;");
                result.AppendLine(4, "return Json(model);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            if (_isSave)
            {
                result.AppendLine();
                result.AppendLine(2, "[HttpGet]");
                result.Append(2, "public virtual ActionResult Edit(");
                var isAdded = false;
                foreach(var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, true), " ", column.Name.ToLowerFirstChar());
                    }
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "var model = new ", singleName, "EditModel();");
                result.Append(3, "if (");
                isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(" || ");
                        result.Append("!", column.Name.ToLowerFirstChar(), ".HasValue");
                    }
                }
                result.AppendLine(")");
                result.AppendLine(3, "{");
                result.AppendLine(4, "model.Errors = new List<ErrorModel>");
                result.AppendLine(5, "{");
                result.AppendLine(6, "new ErrorModel(\"Empty value\")");
                result.AppendLine(5, "};");
                result.AppendLine(4, "return BackBoneJson(model);");
                result.AppendLine(3, "}");
                result.Append(3, "var ", singleName.ToLowerFirstChar(), " = ", serviceName, ".Get", singleName, "ById(");
                isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(column.Name.ToLowerFirstChar(), ".Value");
                    }
                }
                result.AppendLine(");");
                result.AppendLine();
                result.AppendLine(3, "if (null == ", singleName.ToLowerFirstChar(), ")");
                result.AppendLine(3, "{");
                result.AppendLine(4, "model.Errors = new List<ErrorModel>");
                result.AppendLine(5, "{");
                result.Append(6, "new ErrorModel(\"", singleName, " with ");
                isAdded = false;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        if (!isAdded)
                            isAdded = true;
                        else
                            result.Append(", ");
                        result.Append(column.Name.ToLowerFirstChar(), " = \" + ", column.Name.ToLowerFirstChar(), " + \"");
                    }
                }
                result.AppendLine(" was not found.\")");
                result.AppendLine(5, "};");
                result.AppendLine(4, "return BackBoneJson(new ", singleName, "EditModel());");
                result.AppendLine(3, "}");
                result.AppendLine();
                result.AppendLine(3, "model = new ", singleName, "EditModel(", singleName.ToLowerFirstChar(), ");");
                result.AppendLine();
                result.AppendLine(3, "return BackBoneJson(model);");
                result.AppendLine(2, "}");
                result.AppendLine();
                result.AppendLine(2, "[HttpGet]");
                result.AppendLine(2, "public virtual ActionResult EditTemplate()");
                result.AppendLine(2, "{");
                result.AppendLine(3, "return BackBoneView(MVC.Admin.", name, ".Views.Edit, new ", singleName, "EditModel());");
                result.AppendLine(2, "}");
                result.AppendLine();
                result.AppendLine(2, "[HttpPost]");
                result.Append(2, "public virtual ActionResult Edit(");
                foreach(var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        result.Append(GetCSharpType(column.DataTypeName, true), " ", column.Name.ToLowerFirstChar(), ", ");
                    }
                }
                result.AppendLine(singleName, "EditModel model)");
                result.AppendLine(2, "{");
                result.AppendLine(3, "if (!ModelState.IsValid)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "model.Errors = GetErrors;");
                result.AppendLine(4, "return Json(model);");
                result.AppendLine(3, "}");
                result.AppendLine();
                result.AppendLine(3, "var ", singleName.ToLowerFirstChar(), " = (", singleName, ")model;");
                result.AppendLine(3, "try");
                result.AppendLine(3, "{");
                result.AppendLine(4, serviceName, ".Save(", singleName.ToLowerFirstChar(), ");");
                result.AppendLine(4, "model.SuccessMessage = \"", singleName, " has been successfully updated.\";");
                result.AppendLine(4, "return Json(model);");
                result.AppendLine(3, "}");
                result.AppendLine(3, "catch (ServiceException exception)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "ModelState.AddModelError(string.Empty, exception.Message);");
                result.AppendLine(4, "model.Errors = GetErrors;");
                result.AppendLine(4, "return Json(model);");
                result.AppendLine(3, "}");
                result.AppendLine(2, "}");
            }

            if (_isDelete)
            {
                result.AppendLine();
                result.Append(2, "public virtual ActionResult Delete(");
                var isAdded = false;
                var countParameters = 0;
                string firstNotIdentityFieldName = string.Empty;
                foreach (var column in _columns)
                {
                    if (column.IsIdentity)
                    {
                        countParameters++;
                        if (!isAdded)
                        {
                            isAdded = true;
                            firstNotIdentityFieldName = column.Name.ToLowerFirstChar();
                        }
                        else
                            result.Append(", ");
                        result.Append(GetCSharpType(column.DataTypeName, column.IsNullable), "[] ", column.Name.ToPlural().ToLowerFirstChar());
                    }
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "var model = new BaseModel();");

                if (countParameters == 1)
                {
                    result.AppendLine(3, "foreach (var ", firstNotIdentityFieldName, " in ", firstNotIdentityFieldName.ToPlural(), ")");
                    result.AppendLine(3, "{");
                    result.AppendLine(4, "try");
                    result.AppendLine(4, "{");
                    result.AppendLine(5, serviceName, ".Delete(", firstNotIdentityFieldName, ");");
                    
                }
                else
                {
                    result.AppendLine(3, "for (int i = 0; i < ", firstNotIdentityFieldName, ".Length; i++)");
                    result.AppendLine(3, "{");
                    result.AppendLine(4, "try");
                    result.AppendLine(4, "{");
                    result.Append(4, serviceName, ".Delete(");
                    isAdded = false;
                    int i = 0;
                    foreach (var column in _columns)
                    {
                        if (column.IsIdentity)
                        {
                            countParameters++;
                            if (!isAdded)
                                isAdded = true;
                            else
                                result.Append(", ");
                            result.Append(column.Name.ToPlural().ToLowerFirstChar(), "[", i.ToString(), "]");
                            i++;
                        }
                    }
                    result.AppendLine(");");
                }
                result.AppendLine(4, "}");
                result.AppendLine(4, "catch (ServiceException exception)");
                result.AppendLine(4, "{");
                result.AppendLine(5, "ModelState.AddModelError(string.Empty, exception.ToString());");
                result.AppendLine(5, "Trace.TraceError(exception.ToString());");
                result.AppendLine(4, "}");
                result.AppendLine(3, "}");
                result.AppendLine(3, "model.Errors = GetErrors;");
                result.AppendLine(3, "if (ModelState.IsValid)");
                result.AppendLine(3, "{");
                result.AppendLine(4, "model.SuccessMessage = \"", singleName, " have been successfully deleted.\";");
                result.AppendLine(3, "}");
                result.AppendLine(3, "return Json(model);");
                result.AppendLine(2, "}");
            }

            if (_isValidate)
            {
                result.AppendLine();
                result.AppendLine(2, "public virtual ActionResult Validate(");
                result.Append(3, string.Empty);
                var isAdded = false;
                foreach(var column in _columns)
                {
                    if (!isAdded)
                        isAdded = true;
                    else
                        result.Append(", ");
                    result.Append(GetCSharpType(column.DataTypeName, true), " ", column.Name.ToLowerFirstChar());
                }
                result.AppendLine(")");
                result.AppendLine(2, "{");
                result.AppendLine(3, "var result = ", serviceName, ".Validate", singleName, "(");
                result.Append(4, string.Empty);
                isAdded = false;
                foreach (var column in _columns)
                {
                    if (!isAdded)
                        isAdded = true;
                    else
                        result.Append(", ");
                    var defValue = GetDefaultValue(GetCSharpType(column.DataTypeName, column.IsNullable));
                    if (defValue == null)
                    {
                        result.Append(column.Name.ToLowerFirstChar());
                    }
                    else
                    {
                        result.Append(column.Name.ToLowerFirstChar(), " ?? ", defValue);
                    }
                }
                result.AppendLine(");");
                result.AppendLine(3, "return Json(result);");
                result.AppendLine(2, "}");
            }

            result.AppendLine(1, "}");
            result.Append("}");

            return result.ToString();
        }

        public string GenerateEditView()
        {
            var result = new StringBuilder();

            result.AppendLine("@model Web.Areas.Admin.Models.", name, ".", singleName, "EditModel");
            result.AppendLine("<a class=\"close\" href=\"@Url.Action(MVC.Admin.", name, ".Listing())\"></a>");
            result.AppendLine("<form class=\"general\" action=\"#\">");
            result.AppendLine(1, "<legend>Edit ", singleName.ToLowerFirstChar(), "</legend>");
            result.AppendLine(1, "<fieldset>");
            result.AppendLine(2, "@Html.EditorForModel()");
            result.AppendLine(2, "<div class=\"buttons\">");
            result.AppendLine(3, "<button type=\"button\" class=\"cancel\">Cancel</button>");
            result.AppendLine(3, "<button type=\"button\" class=\"save\">Save</button>");
            result.AppendLine(2, "</div>");
            result.AppendLine(1, "</fieldset>");
            result.AppendLine("</form>");

            return result.ToString();
        }

        public string GenerateCreateView()
        {
            var result = new StringBuilder();

            result.AppendLine("@model Web.Areas.Admin.Models.", name, ".", singleName, "CreateModel");
            result.AppendLine("<a class=\"close\" href=\"@Url.Action(MVC.Admin.", name, ".Listing())\"></a>");
            result.AppendLine("<form class=\"general\" action=\"#\">");
            result.AppendLine(1, "<legend>Create ", singleName.ToLowerFirstChar(), "</legend>");
            result.AppendLine(1, "<fieldset>");
            result.AppendLine(2, "@Html.EditorForModel()");
            result.AppendLine(2, "<div class=\"buttons\">");
            result.AppendLine(3, "<button type=\"button\" class=\"cancel\">Cancel</button>");
            result.AppendLine(3, "<button type=\"button\" class=\"save\">Save</button>");
            result.AppendLine(2, "</div>");
            result.AppendLine(1, "</fieldset>");
            result.AppendLine("</form>");

            return result.ToString();
        }

        public string GenerateListingView()
        {
            var result = new StringBuilder();

            result.AppendLine("@using Web.Infrastructure.JSExtensions");
            result.AppendLine("@model BLL.Services.", name, ".Listing.", name, "Listing");
            result.AppendLine("@{");
            result.AppendLine(1, "Layout = null;");
            result.AppendLine("}");
            result.AppendLine("<a class=\"close\" href=\"@Url.Action(MVC.Admin.Content.Dashboard())\"></a>");
            result.AppendLine("<form class=\"filters\" action=\"#\">");
            result.AppendLine(1, "<fieldset>");
            result.AppendLine(2, "<legend>Search:</legend>");
            result.AppendLine(2, "@Html.EditorFor(x => x.Filter)");
            result.AppendLine(3, "<div class=\"buttons\">");
            result.AppendLine(3, "<button class=\"search\" type=\"button\">Search</button>");
            result.AppendLine(3, "<button class=\"reset\" type=\"reset\">Clear</button>");
            result.AppendLine(2, "</div>");
            result.AppendLine(1, "</fieldset>");
            result.AppendLine("</form>");
            result.AppendLine("<form method=\"post\" class=\"listing\" action=\"\" >");
            result.AppendLine(1, "@(Html.GridFor(x => x.", name, ")");
            result.AppendLine(2, ".TableCssClass(\"grid\")");
            foreach (var column in _columns)
            {
                if (column.IsIdentity)
                {
                    result.AppendLine(2, ".Identity(x => x.", column.Name, ")");
                }
                else if (!column.IsPrimaryKey)
                {
                    result.AppendLine(2, ".Column(x => x.", column.Name, ")");
                }
            }
            if (_isSave)
            {
                result.AppendLine(2, ".EditEntityAction(MVC.Admin.", name, ".Edit())");
            }
            result.AppendLine(2, ".NoItemsText(\"There are no ", name.ToLowerFirstChar(), ".\")");
            result.AppendLine(2, ".PaginatorAction(MVC.Admin.", name, ".Listing())");
            result.AppendLine(2, ".Render()");
            result.AppendLine(1, ")");
            result.AppendLine(1, "<div class=\"buttons\">");
            result.AppendLine(2, "<button type=\"button\" class=\"create\">Create</button>");
            result.AppendLine(2, "<button type=\"button\" class=\"delete\">Delete</button>");
            result.AppendLine(1, "</div>");
            result.AppendLine("</form>");

            return result.ToString();
        }

        private static string GetDefaultValue(string cSharpType)
        {
            switch(cSharpType)
            {
                case "int":
                case "double":
                case "byte":
                    return "0";
                case "System.DateTime":
                    return "new System.DateTime()";
                default:
                    return null;
            }
        }

        private static string GetCSharpType(string sqlType, bool isNullable)
        {
            if (sqlType.StartsWith("nvarchar") || sqlType.StartsWith("string"))
            {
                return "string";
            }

            switch (sqlType)
            {
                case "int":
                    return isNullable ? "int?" : "int";
                case "float":
                    return isNullable ? "double?" : "double";
                case "tinyint":
                    return isNullable ? "byte?" : "byte";
                case "date":
                case "datetime":
                case "datetime2":
                    return isNullable ? "System.DateTime?" : "System.DateTime";
                case "binary":
                    return "object";
                default:
                    return "dynamic";
            }
        }
    }
}